Dynamic task parallelism has several variations.
1. Parallel While-Not-Empty
The examples
shown so far in this chapter use techniques that are the parallel
analogs of sequential depth-first traversal. There are also parallel
algorithms for other types of traversals. These techniques rely on
concurrent collections to keep track of the remaining work to be done.
Here’s an example.
public static void ParallelWhileNotEmpty<T>(
IEnumerable<T> initialValues,
Action<T, Action<T>> body)
{
var from = new ConcurrentQueue<T>(initialValues);
while (!from.IsEmpty)
{
var to = new ConcurrentQueue<T>();
Action<T> addMethod = to.Enqueue;
Parallel.ForEach(from, v => body(v, addMethod));
from = to;
}
}
This method shows how you can use Parallel.ForEach
to process an initial collection of values. While processing the
values, additional values to process may be discovered. The additional
values are placed in the to
queue. After the first batch of values processes, the method starts
processing the additional values, which may again result in more values
to process. This process repeats until no additional values can be
produced.
A method that walks a binary tree can use the ParallelWhileNot Empty method.
static void ParallelWalk4<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
ParallelWhileNotEmpty(new[] { tree }, (item, adder) =>
{
if (item.Left != null) adder(item.Left);
if (item.Right != null) adder(item.Right);
action(item.Data);
});
}
An example of a problem that would be an appropriate use of the ParallelWhileNotEmpty
method is a website link checking tool. The walk task loads the initial
page and searches it for links. Each link is checked and removed from
the list, and additional links to unchecked pages from the same site
are added to the list. Eventually, there are no more unchecked links
and the application stops.
2. Task Chaining with Parent/Child Tasks
The TPL includes a task creation option named AttachedToParent. This option is used most frequently in code that uses the dynamic task parallelism pattern. The purpose of the AttachedToParent
option is to link a subtask to the task that created it. In this case,
the subtask is known as a child task and the task that created the
child task is known as a parent task.
You use the AttachedToParent
option in two situations. The first is when you want to link the status
of a parent task to the status of its child tasks. The second is when
you want to use the Microsoft® Visual Studio® development system
debugger to see the parent/ child relationships. The order of execution
is not changed by using the AttachedToParent option.
The following code shows an example.
static void ParallelWalk2<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
var t1 = Task.Factory.StartNew(
() => action(tree.Data),
TaskCreationOptions.AttachedToParent);
var t2 = Task.Factory.StartNew(
() => ParallelWalk2(tree.Left, action),
TaskCreationOptions.AttachedToParent);
var t3 = Task.Factory.StartNew(
() => ParallelWalk2(tree.Right, action),
TaskCreationOptions.AttachedToParent);
Task.WaitAll(t1, t2, t3);
}
The AttachedToParent
option affects the behavior of the parent task. If a parent task with
at least one running child task finishes running for any reason, its Status property becomes WaitingForChildrenToComplete. Only when all of its attached children are no longer running will the parent task’s Status property transition to one of the three final states. Figure 1 illustrates this.
Exceptions from attached children are observed in the parent task.
To access the
parent/child view in the Visual Studio debugger, right-click the row
headings in the Parallel Tasks window, and then click Parent Child View.